#include "pconn.h"

#define HTTP_GET "GET %s HTTP/1.1\r\nHost: %s:%d\r\nKeep-Alive: 300\r\nFrom: %s\r\nConnection: keep-alive\r\n\r\n"
#define HTTP_POST "POST %s HTTP/1.1\r\nHost: %s:%d\r\nContent-Length: %d\r\nContent-Type: application/json\r\nFrom: %s\r\nKeep-Alive: 300\r\nConnection: keep-alive\r\n\r\n%s\r\n"

struct php_cfg{
	char	*log_path;
	int	log_level;
	char	*dst_ip;
	unsigned int	dst_port;
	int	retry_times;
	char	my_ip[INET_ADDRSTRLEN];
};

#define DATA_BUF_SIZE 1<<12

#define	LV_FATAL	0
#define LV_ERROR	1
#define LV_LOG		2
#define LV_DEBUG	3
static char* lv_lables[] = {"\xE2\x98\x85 [FATAL]","\xE2\x98\x86 [ERROR]", "[LOG]", "[DEBUG]",NULL};

static struct php_cfg	cfg;
static FILE	*lfp = NULL;
static int	sfd = 0;
static char	*recvbuf= NULL;
static int	recvbuf_size = 0;
static char	*sendbuf = NULL;
static int	sendbuf_size = 0;
static int	http_code = 0;


/* If you declare any globals in php_test.h uncomment this:
 *    ZEND_DECLARE_MODULE_GLOBALS(test)
 *       */

static function_entry pconn_functions[] = {
	PHP_FE(pconn_req, NULL)
	PHP_FE(pconn_stat, NULL)
	{NULL, NULL, NULL}
};

zend_module_entry pconn_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
	STANDARD_MODULE_HEADER,
#endif
	PCONN_EXTNAME,
	pconn_functions,
	PHP_MINIT(pconn),
	PHP_MSHUTDOWN(pconn),
	NULL/*PHP_RINIT(pconn)*/,
	NULL/*PHP_RSHUTDOWN(pconn)*/,
	NULL,
#if ZEND_MODULE_API_NO >= 20010901
	PCONN_VERSION,
#endif
	STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_PCONN
ZEND_GET_MODULE(pconn)
#endif
PHP_INI_BEGIN()
PHP_INI_ENTRY("pconn.ip","10.60.0.159", PHP_INI_ALL,NULL)
PHP_INI_ENTRY("pconn.port", "8002", PHP_INI_ALL,NULL)
PHP_INI_ENTRY("pconn.log_path", "/tmp/pconn.log", PHP_INI_ALL,NULL)
PHP_INI_ENTRY("pconn.log_level", "1", PHP_INI_ALL,NULL)
PHP_INI_ENTRY("pconn.retry_times", "1", PHP_INI_ALL,NULL)

PHP_INI_END()
/*
static void pconn_init_globals(zend_pconn_globals *pconn_globals)
{
}
*/
/*
PHP_RINIT_FUNCTION(pconn)
{
	return SUCCESS;
}
PHP_RSHUTDOWN_FUNCTION(pconn)
{
	return SUCCESS;
}
*/
PHP_MINIT_FUNCTION(pconn)
{
//	ZEND_INIT_MODULE_GLOBALS(pconn, pconn_init_globals, NULL);

	REGISTER_INI_ENTRIES();
	cfg.log_path = INI_STR("pconn.log_path");
	cfg.log_level = INI_INT("pconn.log_level");
	cfg.dst_ip = INI_STR("pconn.ip");
	cfg.dst_port= INI_INT("pconn.port");
	cfg.retry_times = INI_INT("pconn.retry_times");
	get_my_addr(cfg.my_ip, INET_ADDRSTRLEN);

	sfd = 0;
	
	lfp = fopen(cfg.log_path,"a+");
	if(lfp == NULL){
		lfp = fopen("/tmp/pconn.log","a+");
		logit(LV_ERROR, "can't open %s", INI_STR("pconn.log_path"));
	}

	recvbuf = (char *)pemalloc(DATA_BUF_SIZE, 1);
	if(recvbuf != NULL){
		recvbuf_size = DATA_BUF_SIZE;
	}else{
		recvbuf_size = 0;
		logit(LV_FATAL, "can't alloc recvbuf");
		return FAILURE;
	}

	sendbuf = (char *)pemalloc(DATA_BUF_SIZE, 1);
	if(sendbuf != NULL){
		sendbuf_size = DATA_BUF_SIZE;
	}else{
		sendbuf_size = 0;
		logit(LV_FATAL, "can't alloc sendbuf");
		return FAILURE;
	}
	return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(pconn)
{
	UNREGISTER_INI_ENTRIES();
	if(sfd != 0){
		pconn_close(sfd);
	}
	if(lfp != NULL){
		fclose(lfp);
	}
	if(recvbuf != NULL){
		pefree(recvbuf, 1);
	}
	return SUCCESS;
}

PHP_FUNCTION(pconn_stat)
{
	RETURN_LONG(http_code);
}

PHP_FUNCTION(pconn_req)
{
	char	*url = NULL;
	int	url_len = 0;
	char	*vars = NULL;
	int	vars_len = 0;
	int	header_len = 0;

	if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &url, &url_len, &vars, &vars_len) == FAILURE ){
                logit(LV_ERROR, "parse parameters");
		http_code = -1;
		RETURN_LONG(-1);
	}
	if(url_len == 0){
		logit(LV_ERROR, "empty url");
		http_code= -2;
		RETURN_LONG(-2);
	}
	if(vars_len == 0){
		header_len = strlen(HTTP_GET) + url_len + INET_ADDRSTRLEN*2 + 5;
	}if(vars_len > 10000){
                logit(LV_ERROR, "vars too long[vars_len: %d]", vars_len);
		http_code= -3;
                RETURN_LONG(-3);
	}else{
		header_len = strlen(HTTP_POST) + url_len + INET_ADDRSTRLEN*2 + vars_len + 10;
	}
	
	if(header_len > sendbuf_size -1){
		sendbuf = perealloc(sendbuf, header_len+1, 1);
		if(recvbuf != NULL){
			sendbuf_size = header_len + 1;
			logit(LV_LOG, "realloc [sendbuf:%x]",sendbuf);
		}else{
			sendbuf_size = 0;
			logit(LV_ERROR, "realloc sendbuf failed");
			http_code = -4;
			RETURN_LONG(-4);
		}
	}

	if(vars_len == 0){
		snprintf(sendbuf, sendbuf_size, HTTP_GET, url, cfg.dst_ip, cfg.dst_port, cfg.my_ip);
	}else{
		snprintf(sendbuf, sendbuf_size, HTTP_POST, url, cfg.dst_ip, cfg.dst_port, vars_len+2, cfg.my_ip, vars);
	}

	logit(LV_LOG, "<<<");

	int retry_times = 0;
	int ret = 0;
	do{
		ret = request(sendbuf, strlen(sendbuf));
	}while(ret<0 && retry_times++ < cfg.retry_times);

	if(ret < 0){
		http_code = -100 + ret;
		RETURN_LONG(-100 + ret);
	}
	header_len = ret;

	logit(LV_LOG, ">>>\n");

	if(lfp != NULL){
		fflush(lfp);
	}
	RETURN_STRING(recvbuf+header_len, 1);
}

int request(char *buf, int len)
{
	int ret = 0;
	int offset = 0;
	int total_len = 0;
	int header_len = 0;

	if(sfd <= 0){
		sfd = pconn_init(cfg.dst_ip, cfg.dst_port);
	}
	if(sfd <= 0){
		return -1;
	}
	logit(LV_DEBUG, "send: \n%s", buf);

	while(len != 0){
		ret = pconn_send(sfd, buf+offset, len);
		if(ret <= 0){
           	pconn_close(sfd);
			sfd = 0;
			return -2;
		}
		len -= ret;
		offset += ret;
//		logit(LV_ERROR, "send left %d\n", len);
	}
	http_code = 0;
	memset(recvbuf, 0 ,recvbuf_size);
	offset = 0;

	while(total_len == 0){// until recv whole header
		ret = pconn_recv(sfd, recvbuf, recvbuf_size-1);
		if(ret <= 0){
        	        pconn_close(sfd);
			sfd = 0;
			return -3;
		}
		offset = ret;
		recvbuf[offset] = '\0';

		total_len = parse_len(recvbuf, &header_len);
		if(total_len == 0){//body not found
			continue;
		}
		if(total_len < 0 ){
	                pconn_close(sfd);
			sfd = 0;
			return -4;
		}
		if(total_len > recvbuf_size-1){
			recvbuf = perealloc(recvbuf, total_len+1, 1);
			if(recvbuf != NULL){
				recvbuf_size = total_len + 1;
				logit(LV_LOG, "realloc [buf:%x]",recvbuf);
			}else{
				recvbuf_size = 0;
				logit(LV_ERROR, "realloc failed");
				return -5;
			}
		}
	}
	len = total_len - offset;//left len
	logit(LV_LOG, "recv %d left %d", offset, len);
	while(len > 0){
		ret = pconn_recv(sfd, recvbuf+offset, len);
		if(ret <= 0){
                	pconn_close(sfd);
			sfd = 0;
			return -6;
		}
		len -= ret;
		offset += ret;
//		logit(LV_LOG, "recv %d left %d", offset, len);
	}
	recvbuf[offset] = '\0';
	logit(LV_DEBUG, "recv: \n%s", recvbuf);
	return header_len;
}

int logit(int level, const char *fmt, ...)
{
	if(lfp == NULL){
		return -1;
	}
	if(level > cfg.log_level){
		return 0;
	}
	fprintf(lfp, lv_lables[level]);

	time_t t = time(NULL);
	struct tm tm;
	localtime_r(&t, &tm);
	fprintf(lfp, "[%.2d:%.2d:%.2d %.2d/%.2d][%d]: ", tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_mon+1, tm.tm_mday, getpid());
	va_list argp;
	va_start(argp, fmt);
	vfprintf(lfp, fmt, argp);
	va_end(argp);
	fprintf(lfp, "\n");
	return 0;
} 

int pconn_init(const char* destip, unsigned int destport)
{
	int ret = 0;
	int sfd = 0;
	struct sockaddr_in dest;
	
	if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		logit(LV_ERROR, "Init Socket [ERR:%s]",strerror(errno));
		return -1;
	}

	memset(&dest, 0, sizeof(dest));
	dest.sin_family = AF_INET;
	dest.sin_port = htons(destport);
	if (inet_aton(destip, (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
		logit(LV_ERROR, "inet_aton [ip:%s]",destip);
		return -2;
	}

	if (connect(sfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
		logit(LV_ERROR, "Connect server [ip:%s][port:%d][ERR:%s]",destip,destport,strerror(errno));
		close(sfd);
		return -3;
	}

	ret= fcntl( sfd, F_GETFL, 0 );
	if (ret < 0){
		logit(LV_ERROR, "F_GETFL [ERR:%s]",strerror(errno));
		return  -4;
	}

	ret |= O_NONBLOCK;
	fcntl( sfd, F_SETFL, ret );

	return sfd;
}

int pconn_send(int sfd, char * buf, int len)
{
	int ret = 0;
	struct timeval timeout;
	fd_set wset;

	timeout.tv_sec = 5;
	timeout.tv_usec = 0;

	FD_ZERO( &wset );
	FD_SET( (unsigned int)sfd, &wset );
	
	ret = select( sfd+1, NULL, &wset, NULL, &timeout );
	if(ret == 0){
		logit(LV_ERROR,  "pconn_send: timeout [socket:%u][ERR:%s]", sfd, strerror( errno ));
		return 0;
	}
	if(ret < 0){
		logit(LV_ERROR,  "pconn_send: select [socket:%u][ERR:%s]", sfd, strerror( errno ));
		return -1;
	}
	ret = send(sfd, buf, len, 0);
	if(ret <= 0){
		if ( errno == EWOULDBLOCK ){
			logit(LV_ERROR,  "pconn_send: block [socket:%u][ERR:%s]", sfd, strerror( errno ));
			return 0;
		}else{
			logit(LV_ERROR, "pconn_send: [len:%u][ERR:%s]", len, strerror( errno ) );
			return -2;
		}
	}
	return ret;
}

int pconn_recv(int sfd, char * buf, int len)
{
	int ret = 0;
	struct timeval timeout;
	fd_set rset;

	timeout.tv_sec = 5;
	timeout.tv_usec = 0;

	FD_ZERO( &rset );
	FD_SET( (unsigned int)sfd, &rset );
	
	ret = select( sfd+1, &rset, NULL, NULL, &timeout );
	if(ret == 0){
		logit(LV_ERROR, "pconn_recv: select timeout [socket:%u][ERR:%s]", sfd, strerror( errno ));
		return -1;
	}
	if(ret < 0){
		logit(LV_ERROR, "pconn_recv: select [socket:%u][ERR:%s]", sfd, strerror( errno ));
		return -2;
	}
	ret = recv(sfd, buf, len, 0);
	if(ret < 0){
		if (errno == EWOULDBLOCK){
			return 0;
		}else{
			logit(LV_ERROR, "pconn_recv: [len:%u][ERR:%s]", len, strerror( errno ) );
			return -3;
		}
	}
	if(ret == 0){
                logit(LV_ERROR, "pconn_recv: peer closed[ERR:%s]", strerror( errno ) );
                return -4;
        }
	return ret;
}

int pconn_close(int sfd)
{
	if ( sfd > 0 ){
		shutdown( sfd, SHUT_RDWR );
		close( sfd );
	}
	logit(LV_LOG, "!!close: %d",sfd);
	return 0;
}

/*****************************************************
 * parse http packet to get total_len
 * no Contant-Length area is illegal
 * return total_len on success, 0 on body nofound or <0 on failure
 * ***************************************************/
int parse_len(const char *buf, int *header_len)
{
	int ret = 0;
	double version = 0;
	int content_len = 0;
	char *p = NULL;
//find body first
	p = strstr(buf, "\r\n\r\n");
	if(p == NULL){
		logit(LV_ERROR, "parse_len: no Contant [buf:%d]", buf);
		return 0;
	}
	*header_len = (p - buf) + 4;
//find http_code
	ret = sscanf(buf, "HTTP/%f %d%[^ ]", &version, &http_code);
	if(ret <= 0){
		logit(LV_ERROR, "parse_len: no HTTP/1.x [http_code] ");
		return -1;
	}
//find Content-length	
	p = strstr(buf, "Content-Length:");
	if(p == NULL){
		logit(LV_ERROR, "parse_len: no Contant-Length [buf: %s]",buf);
		//no Contant-Length will assume 0;
	}else{
		ret = sscanf(p, "Content-Length: %d\r\n", &content_len);
		if(ret <= 0){
			logit(LV_ERROR, "parse_len: Contant-Length cann't be sscanf, ret = %d", ret);
			return -2;
		}
	}
	
	logit(LV_LOG, "header-len:%d, content-len:%d", *header_len, content_len);
	return *header_len + content_len;
}

int get_my_addr(char *addr, int len)
{
	int ret = 0;
	char hostname[64] = {0};
	struct hostent *hep = NULL;
	char ** addr_list;

	ret = gethostname(hostname, 64);
	if(ret < 0){
		logit(LV_ERROR, "gethostname [ERR: %s]", strerrno(errno));
		return -1;
	}

	hep = gethostbyname(hostname);
	if(hep == NULL){
		logit(LV_ERROR, "gethostbyname [ERR: %s]", hstrerror(h_errno));
		return -2;
	}

	addr_list = hep->h_addr_list;
	for(; *addr_list!=NULL; addr_list++){
		if(inet_ntop(hep->h_addrtype, *addr_list, addr, len) == NULL){
			logit(LV_ERROR, "inet_ntop [ERR: %s]", strerrno(errno));
			return -3;
		}
		if(strncmp(addr, "127.0.0.1", INET_ADDRSTRLEN-1) != 0){
			return 0;
		}
	}
	strncpy(addr, "127.0.0.1", INET_ADDRSTRLEN-1);
	logit(LV_ERROR, "no invalid ip");
	return -4;
}


